/*-*-C-*-
 * Window CSE protocol support
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "dbox.h"
#include "newmsgs.h"
#include "registry.h"

#include "format.h"
#include "relocate.h"
#include "windowedit.h"
#include "gadget.h"
#include "keycuts.h"
#include "props.h"
#include "protocol.h"
#include "toolbars.h"



/* OUTGOING MESSAGES */


/*
 * Called whenever the user modifies a window or a window entry.
 * Checks the status of our private "modified" flag, and
 * sends a message to the shell if the flag is going from
 * FALSE to TRUE.
 */

error * protocol_send_resed_object_modified (WindowObjPtr window)
{
    if (window->modified == FALSE)
    {
        MessageResEdObjectModifiedRec modified;
        window->modified = TRUE;
        modified.header.yourref = 0;
        modified.header.size = sizeof (modified);
        modified.header.messageid = MESSAGE_RESED_OBJECT_MODIFIED;
        modified.flags = 0;
        modified.documentID = window->documentID;
        modified.objectID = window->objectID;
        ER ( swi (Wimp_SendMessage,
                  R0, EV_USER_MESSAGE,
                  R1, &modified,
                  R2, parenttaskhandle,  END) );
    }
    return NULL;
}
        

/*
 * We are about to close an object.  Shell should clear it's "I am editing
 * this object" flag in response to this.  This function is called by
 * windowedit_close_window().
 */

error * protocol_send_resed_object_closed (WindowObjPtr window)
{
    MessageResEdObjectClosedRec closed;

    closed.header.yourref = 0;
    closed.header.size = sizeof (closed);
    closed.header.messageid = MESSAGE_RESED_OBJECT_CLOSED;
    closed.flags = 0;
    closed.documentID = window->documentID;
    closed.objectID = window->objectID;
    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &closed,
                R2, parenttaskhandle,  END);
}


/*
 * We call this when we want to proactively send an object back to the shell,
 * normally when the user has pressed the window's CLOSE icon.
 * The shell will respond with RESED_OBJECT_LOADED, which is handled
 * by protocol_incoming_message().
 * If window->pendingclose is set, then windowedit_close_window(window, TRUE)
 * will be called if and when the send is successful.
 *
 * NB this function does not check window->modified.  The caller
 * can do so and bypass this function if wanted.
 */

error * protocol_send_resed_object_sending (WindowObjPtr window)
{
    MessageResEdObjectSendingRec sending;
    int bodysize, stringsize, msgsize, numrelocs;
    int size = windowedit_object_size (window, &bodysize, &stringsize,
                                               &msgsize, &numrelocs);
    error *err;

    if (window->objectdata) free(window->objectdata);
    window->objectdata = malloc (size);
    if (window->objectdata == NULL)
    {
        /* Oh dear!  We have nowhere to store the data to send it back.
         * Show the user an error message, and cancel the pending close flag.
         */
        window->pendingclose = FALSE;
        return error_lookup ("NoMem");
    }
    else
    {
        err = windowedit_save_object_to_memory (window, window->objectdata,
                                   bodysize, stringsize, msgsize, numrelocs);
        if (err)
        {
            /* Something went wrong.  Free up the temp buffer, clear
             * the pendingclose flags and return an error
             */
            free (window->objectdata);
            window->objectdata = NULL;
            window->pendingclose = FALSE;
            return err;
        }
    }
    sending.header.yourref = 0;
    sending.header.size = sizeof (sending);
    sending.header.messageid = MESSAGE_RESED_OBJECT_SENDING;
    sending.flags = 0;
    sending.documentID = window->documentID;
    sending.objectID = window->objectID;
    sending.address = (unsigned int) window->objectdata;
    sending.size = size;     /* address and size valid only if flags == 0 */
    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE_RECORDED,
                R1, &sending,
                R2, parenttaskhandle,  END);
}
    

/*
 * We have received a DataSave message.  The only thing we accept is
 * something being dragged from our Document window of our parent shell.
 * We determine this by replying to the DataSave message with
 * a RESED_OBJECT_NAME_REQUEST message.  If the sender was our
 * shell it will reply with RESED_OBJECT_NAME, enabling us to
 * fill in a name field in our dbox.
 */

error * protocol_send_resed_object_name_request (MessageDataSavePtr save)
{
    MessageResEdObjectNameRequestRec req;
    int handle = save->windowhandle;
    int icon = save->iconhandle;
    void *closure;
    RegistryType type = registry_lookup_window (handle, (void **) &closure);
    WindowObjPtr window;

    switch (type)
    {
    case MainpropsDbox:
        window = (WindowObjPtr) closure;

        /* see if it's an acceptable icon */
        if (!props_main_drop_icon (icon))
            return NULL;

        break;

    case GadgetDbox:
        window = ((GadgetPtr) closure)->owner;

        /* see if gadget is interested */
        if (!gadget_drop_icon ((GadgetPtr) closure, icon))
            return NULL;

        break;

    case ToolbarsDbox:
        window = (WindowObjPtr) closure;

        /* see if it's an acceptable icon */
        if (!toolbars_drop_icon (icon))
            return NULL;

        break;

    case ShortcutsDbox:
        window = (WindowObjPtr) closure;

        /* see if it's an acceptable icon */
        if (!keycuts_drop_icon (icon))
            return NULL;

        break;
    default:
        return NULL;
    }

    /* send out request for object's name */
    req.header.yourref = save->header.myref;
    req.header.size = sizeof (req);
    req.header.messageid = MESSAGE_RESED_OBJECT_NAME_REQUEST;
    req.flags = 0;
    req.documentID = window->documentID;
    req.objectID = window->objectID;
    req.windowhandle = handle;
    req.iconhandle = icon;

    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &req,
                R2, save->header.taskhandle,  END);
}



/* INCOMING MESSAGES */


/*
 * Use the document and object IDs from an incoming message to
 * locate our corresponding WindowObjPtr.  Return it, or NULL if not found.
 */

static WindowObjPtr locate_window (Opaque documentID, Opaque objectID)
{
    int i = 0;
    RegistryType type;
    void *closure;

    while ((i = registry_enumerate_windows (i, &type, NULL, &closure)) != 0)
        if (type == WindowEdit)
        {
            WindowObjPtr window = (WindowObjPtr) closure;
            if ( window->documentID == documentID &&
                 window->objectID == objectID )
                return window;
        }
    return NULL;
}


/*
 * The shell is asking us to open an object or raise it.  Fetch the
 * data using Wimp_TransferBlock and get it loaded into an editing
 * window.
 */

static error * received_resed_object_load (MessageResEdObjectLoadPtr load)
{
    WindowObjPtr window = locate_window (load->documentID, load->objectID);
    MessageResEdObjectLoadedPtr loaded = (MessageResEdObjectLoadedPtr) load;
    char *object = NULL;
    error *err;

    /*
     * This message should not have found us unless it is a Window object.
     * Check, and refuse to load it if it isn't
     */

    if (load->class != WINDOW_OBJECT_CLASS)
    {
        loaded->flags = BIT(1);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        err = error_lookup ("BadClass", load->class);
        goto reply;
    }

    /* If we already have this loaded, and the force bit is clear,
     * then just raise the window editing window, and reply with
     * "success" regardless of whether an error occurred.
     */

    if (window && (load->flags & BIT(0)) == 0)
    {
        err = windowedit_raise_window (window);
        loaded->flags = 0;
        goto reply;
    }

    switch (load->version)
    {
    case 101:
    case WINDOW_OBJECT_VERSION:
        break;

    /* only the versions above can be handled by this version of !Window */
    default:
        {
            char ver[20];
            sprintf (ver, "%d.%02d", load->version/100, load->version%100);
            loaded->flags = BIT(1);
            loaded->errcode = OBJECT_LOADED_ERROR_VERSION;
            err = error_lookup ("BadVersion", ver);
            goto reply;
        }
    }

    object = malloc (load->size);
    if (object == NULL)
    {
        loaded->flags = BIT(1);
        loaded->errcode = OBJECT_LOADED_ERROR_NOMEM;
        err = error_lookup ("NoMem");
        goto reply;
    }

    err = swi (Wimp_TransferBlock,
               R0, load->header.taskhandle,
               R1, load->address,
               R2, taskhandle,
               R3, object,
               R4, load->size,  END);
    if (err)
    {
        loaded->flags = BIT(0);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        goto reply;
    }

    /* pre-process earlier object versions where necessary */
    switch (load->version)
    {
    case 101: /* version 102 window objects include:
                     showevent and hideevent fields
                     toolbar fields (4)              */
        /* reallocate the space to get an extra 56 bytes:
                     6*4 = 24 bytes for the new fields;
                     4*8 = 32 bytes for 4 new relocation records */
        {
            char *p = realloc (object, load->size + 56);
            if (p == NULL)
            {
                free (object);
                loaded->flags = BIT(1);
                loaded->errcode = OBJECT_LOADED_ERROR_NOMEM;
                err = error_lookup ("NoMem");
                goto reply;
            }

            object = p;
        }

        /* insert the new fields */
        {
            int i;
            int base = sizeof (ResourceFileObjectTemplateHeaderRec) +
                       offsetof (WindowTemplateRec, showevent);

            /* move up to make space for them */
            for (i = load->size - 1; i >= base; i--)
                object[i+24] = object[i];
        }

        /* adjust various offsets in the newly arranged object */
        {
            ResourceFileObjectTemplateHeaderPtr x =
                (ResourceFileObjectTemplateHeaderPtr) object;

            /* increment pointers to the string, message and reloc tables */
            if (x->stringtableoffset >= 0)
                x->stringtableoffset += 24;
            if (x->messagetableoffset >= 0)
                x->messagetableoffset += 24;
            if (x->relocationtableoffset >= 0)
                x->relocationtableoffset += 24;

            /* increment necessary fields in the object template header */
            x->hdr.totalsize += 24;
            x->hdr.bodysize += 24;

            /* increment necessary fields in window template */
            {
                WindowTemplatePtr w = (WindowTemplatePtr) (x + 1);

                /* set default values into the new fields */
                w->showevent = 0;
                w->hideevent = 0;
                w->toolbaribl = -1;   /* NULL StringReference values */
                w->toolbaritl = -1;
                w->toolbarebl = -1;
                w->toolbaretl = -1;

                /* adjust offsets of keyboardshortcuts and gadgets lists */
                w->keyboardshortcuts += 24;
                w->gadgets += 24;
            }

            /* update relocations as necessary */
            {
                RelocationTablePtr t =
                    (RelocationTablePtr) (object + x->relocationtableoffset);
                RelocationPtr r = t->relocations;
                int n = t->numrelocations;
                int i = 0;

                while (i < n)
                {
                    if (r->wordtorelocate >
                              offsetof (WindowTemplateRec, showevent))
                        r->wordtorelocate += 24;
                    r++;
                    i++;
                }

                /* add 4 new relocation records for the new toolbar fields */
                t->numrelocations += 4;

                r->directive = RELOCATE_STRINGREFERENCE;
                r->wordtorelocate = offsetof (WindowTemplateRec, toolbaribl);
                r++;
                r->directive = RELOCATE_STRINGREFERENCE;
                r->wordtorelocate = offsetof (WindowTemplateRec, toolbaritl);
                r++;
                r->directive = RELOCATE_STRINGREFERENCE;
                r->wordtorelocate = offsetof (WindowTemplateRec, toolbarebl);
                r++;
                r->directive = RELOCATE_STRINGREFERENCE;
                r->wordtorelocate = offsetof (WindowTemplateRec, toolbaretl);
            }
        }
        break;

    default:
        break;
    }

    /*
     * If window is NULL, then windowedit_open creates a new Window
     * structure.
     * If window is non-NULL, it loads the new data into an existing one.
     * If it returns an error, the window is not open.
     */

    err = windowedit_load ( window,
                            (ResourceFileObjectTemplateHeaderPtr) object,
                            load );
    if (err)
    {
        loaded->flags = BIT(0);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        goto reply;
    }

    loaded->flags = 0;          /* Success! */

reply:

    /*
     * If an error occurred, loaded->{flags, errcode} have already been
     * set up correctly.  Formulate the reply message in the same memory as
     * the incoming one.
     */

    free (object);
    loaded->header.yourref = load->header.myref;
    loaded->header.size = sizeof (MessageResEdObjectLoadedRec);
    loaded->header.messageid = MESSAGE_RESED_OBJECT_LOADED;
    (void) swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, loaded,
                R2, load->header.taskhandle,  END);

    /* Finally raise any error that we suffered, because the shell
     * will not tell the user.
     */
    return err;
}



/*
 * The shell is requesting the data associated with some object.
 * Send it along.
 */

static error * received_resed_object_send (MessageResEdObjectSendPtr send)
{
    WindowObjPtr window = locate_window (send->documentID, send->objectID);
    MessageResEdObjectSendingRec sending;
    int size = 0;
    error *err = NULL;

    if (window == NULL)
    {
        err = error_lookup ("UnkObj");
        sending.errcode = OBJECT_SENDING_ERROR_UNKNOWN;
    }
    else
    {
        int bodysize, stringsize, msgsize, numrelocs;

        size = windowedit_object_size (window, &bodysize, &stringsize,
                                                   &msgsize, &numrelocs);

        if (window->objectdata) free(window->objectdata);
        window->objectdata = malloc (size);
        if (window->objectdata == NULL)
        {
            /* Bother!  We have nowhere to store the data to send it back.
             * Show the user an error message, and reply with an error
             * indication to the shell.
             */
            sending.errcode = OBJECT_SENDING_ERROR_NOMEM;
            err = error_lookup ("NoMem");
        }
        else
        {
            err = windowedit_save_object_to_memory ( window,
                                          window->objectdata, bodysize,
                                          stringsize, msgsize, numrelocs );
            if (err)
            {
                free (window->objectdata);
                window->objectdata = NULL;
                sending.errcode = OBJECT_SENDING_ERROR_NONFATAL;
            }
        }
    }

    /* set 'pendingclose' flag if shell has requested that the object be
       deleted after a successful transfer */
    if (err == NULL && (send->flags & BIT(0)) != 0)
        window->pendingclose = TRUE;

    /* Formulate reply */
    sending.header.size = sizeof(MessageResEdObjectSendingRec);
    sending.header.yourref = send->header.myref;
    sending.header.messageid = MESSAGE_RESED_OBJECT_SENDING;
    sending.flags = err ? BIT(0) : 0;
    sending.documentID = send->documentID;
    sending.objectID = send->objectID;
    sending.address = (unsigned int) window->objectdata;
    sending.size = size;     /* address and size valid only if flags == 0 */
    (void) swi (Wimp_SendMessage,
                R0, err ? EV_USER_MESSAGE : EV_USER_MESSAGE_RECORDED,
                R1, &sending,
                R2, send->header.taskhandle,  END);

    return err;
}
    

/*
 * The shell has loaded the object we were sending it - or else generated an
 * error (in which case the user has already been told).  Free up our
 * temporary space and if the window is waiting to be closed, then close it
 * (unless there was an error indication in the message).
 */

static error * received_resed_object_loaded (
                        MessageResEdObjectLoadedPtr loaded )
{
    WindowObjPtr window = locate_window ( loaded->documentID,
                                          loaded->objectID );

    if (window)
    {
        if (window->objectdata) free(window->objectdata);
        window->objectdata = NULL;

        /* Check the error indication.  If there was no error, then
         * go ahead and close the object if our pendingclose is set.
         */

        if ((loaded->flags & BIT(0)) == 0)
        {
            window->modified = FALSE;
            if (window->pendingclose)
            {
                /* The TRUE means "send a RESED_OBJECT_CLOSED message to
                   the shell" */
                return windowedit_close_window (window, TRUE);
            }
        }
        window->pendingclose = FALSE;
    }
    return  NULL;
}


/*
 * The shell is telling us that an object's name has changed.
 */

static error * received_resed_object_renamed (
                           MessageResEdObjectRenamedPtr rename )
{
    WindowObjPtr window = locate_window ( rename->documentID,
                                          rename->objectID );

    if (window)
        return windowedit_rename_window (window, rename->name);
    else
        return NULL;
}


/*
 * The shell is telling us that an object it thinks we have open
 * is being deleted.  Close any window representing that window,
 * and free up resources etc.  Do not reply to the message.
 */

static error * received_resed_object_deleted (
                              MessageResEdObjectDeletedPtr delete )
{
    WindowObjPtr window = locate_window ( delete->documentID,
                                          delete->objectID );

    if (window)
        /* The FALSE means "don't send a RESED_OBJECT_CLOSED message to the
           shell" */
        return windowedit_close_window (window, FALSE);
    else
        return NULL;
}


/*
 * The shell is telling us the name of an object.  This will be in response
 * to us sending RESED_OBJECT_NAME_REQUEST in reply to a DataSave message.
 */

static error * received_resed_object_name (MessageResEdObjectNamePtr name)
{
    /* check that there was no error */
    if (name->flags == 0)
    {
        int handle = name->windowhandle;
        int icon = name->iconhandle;
        char *objname = name->name;
        ObjectClass objclass = name->class;
        void *closure;
        RegistryType type = 
            registry_lookup_window (handle, (void **) &closure);

        if (type == MainpropsDbox)
            return props_main_object_drop ((WindowObjPtr) closure,
                                           icon, objclass, objname);

        if (type == GadgetDbox)
            return gadget_object_drop ((GadgetPtr) closure,
                                       icon, objclass, objname);

        if (type == ToolbarsDbox)
            return toolbars_object_drop ((WindowObjPtr) closure,
                                         icon, objclass, objname);

        if (type == ShortcutsDbox)
            return keycuts_object_drop ((WindowObjPtr) closure,
                                         icon, objclass, objname);
        return NULL;
    }
    else
        return NULL;
}


/*
 * The shell has loaded new sprites.  Force a redisplay of all
 * the WindowEdit windows that are open
 */

static error * received_resed_sprites_changed (
                               MessageResEdSpritesChangedPtr sprites )
{
    int i = 0;
    RegistryType type;
    void *closure;

    while ((i = registry_enumerate_windows (i, &type, NULL, &closure)) != 0)
        if (type == WindowEdit)
        {
            WindowObjPtr window = (WindowObjPtr) closure;
            ER ( swi (Wimp_ForceRedraw,
                      R0,  window->window->handle,
                      R1,  window->window->workarea.minx,
                      R2,  window->window->workarea.miny,
                      R3,  window->window->workarea.maxx,
                      R4,  window->window->workarea.maxy,  END) );
        }
    return NULL;
}


/*
 * Our attempt to send an object to the shell bounced.  Clear up
 * the temporary buffer that the object has allocated, and continue.
 */

static error * bounced_resed_object_sending (
                             MessageResEdObjectSendingPtr sending )
{
    WindowObjPtr window = locate_window ( sending->documentID,
                                          sending->objectID );

    if (window)
    {
        if (window->objectdata) free(window->objectdata);
        window->objectdata = NULL;
        window->pendingclose = FALSE;
    }
    return NULL;
}


/*
 * Protocol message dispatcher.  The main event handling code uses this
 * to handle all ResEd shell<->CSE protocol messages.  Buf points
 * to a 64-word Wimp event buffer.  The event code is in 'event'.
 * Sets *handled to indicate to the caller whether the event has
 * been handled or not.
 */

error * protocol_incoming_message (int event, int *buf, Bool *handled)
{
    MessageHeaderPtr hdr = (MessageHeaderPtr) buf;
    if (handled) *handled = TRUE;
    switch (event)
    {
    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_LOAD:
            return received_resed_object_load (
                                   (MessageResEdObjectLoadPtr) buf );
        case MESSAGE_RESED_OBJECT_SEND:
            return received_resed_object_send (
                                   (MessageResEdObjectSendPtr) buf );
        case MESSAGE_RESED_OBJECT_LOADED:
            return received_resed_object_loaded (
                                   (MessageResEdObjectLoadedPtr) buf );
        case MESSAGE_RESED_OBJECT_RENAMED:
            return received_resed_object_renamed (
                                   (MessageResEdObjectRenamedPtr) buf );
        case MESSAGE_RESED_OBJECT_DELETED:
            return received_resed_object_deleted (
                                   (MessageResEdObjectDeletedPtr) buf );
        case MESSAGE_RESED_OBJECT_NAME:
            return received_resed_object_name (
                                   (MessageResEdObjectNamePtr) buf );
        case MESSAGE_RESED_SPRITES_CHANGED:
            return received_resed_sprites_changed (
                                   (MessageResEdSpritesChangedPtr) buf );
        }
        break;
    case EV_USER_MESSAGE_ACKNOWLEDGE:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_SENDING:
            return bounced_resed_object_sending (
                                   (MessageResEdObjectSendingPtr) buf );
        }
        break;
    }
    if (handled) *handled = FALSE;
    return NULL;
}
